﻿//---------------------------------------------------------------------------
// <copyright file="RibbonQuickAccessToolBar.cs" company="Microsoft Corporation">
//     Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------

namespace Microsoft.Windows.Controls.Ribbon
{
	#region Using declarations

	using Microsoft.Windows.Automation.Peers;
	using Microsoft.Windows.Controls.Ribbon.Primitives;
	using System;
	using System.Collections.Generic;
	using System.Collections.Specialized;
	using System.ComponentModel;
	using System.Windows;
	using System.Windows.Automation.Peers;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Data;
	using System.Windows.Input;

	#endregion

	/// <summary>
	///   Implements the Ribbon's QuickAccessToolbar.
	/// </summary>
	[TemplatePart(Name = MainPanelTemplateName, Type = typeof(RibbonQuickAccessToolBarPanel))]
	[TemplatePart(Name = OverflowPanelTemplateName, Type = typeof(RibbonQuickAccessToolBarOverflowPanel))]
	[TemplatePart(Name = OverflowPopupTemplateName, Type = typeof(Popup))]
	[TemplatePart(Name = OverflowButtonTemplateName, Type = typeof(RibbonToggleButton))]
	public class RibbonQuickAccessToolBar : ItemsControl
	{
		#region Fields

		private const string MainPanelTemplateName = "PART_MainPanel";
		private const string OverflowPanelTemplateName = "PART_OverflowPanel";
		private const string OverflowPopupTemplateName = "PART_OverflowPopup";
		private const string OverflowButtonTemplateName = "PART_OverflowButton";

		private RibbonQuickAccessToolBarPanel _mainPanel;
		private RibbonQuickAccessToolBarOverflowPanel _overflowPanel;
		private Popup _overflowPopup;                                   // The Popup that hosts the overflow panel.
		private RibbonToggleButton _overflowButton;                     // The ToggleButton that hosts the overflow panel popup.
		private BitVector32 _bits = new BitVector32(0);
		private static string _overflowButtonToolTipText = SR.Get(SRID.RibbonQuickAccessToolBar_OverflowButtonToolTip);

		private enum Bits
		{
			InContextMenu = 0x01,
			RetainFocusOnEscape = 0x02,
		}

		private bool InContextMenu
		{
			get { return _bits[(int)Bits.InContextMenu]; }
			set { _bits[(int)Bits.InContextMenu] = value; }
		}

		private bool RetainFocusOnEscape
		{
			get { return _bits[(int)Bits.RetainFocusOnEscape]; }
			set { _bits[(int)Bits.RetainFocusOnEscape] = value; }
		}

		#endregion

		#region Panels

		internal RibbonQuickAccessToolBarPanel MainPanel
		{
			get
			{
				return _mainPanel;
			}
		}

		internal RibbonQuickAccessToolBarOverflowPanel OverflowPanel
		{
			get
			{
				return _overflowPanel;
			}
		}

		#endregion Panels

		#region Constructors

		/// <summary>
		///   Initializes static members of the RibbonQuickAccessToolBar class.
		/// </summary>
		static RibbonQuickAccessToolBar()
		{
			Type ownerType = typeof(RibbonQuickAccessToolBar);
			DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));
			FocusableProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));
			DataContextProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(null, OnCoerceDataContext));

			// If the DirectionalNaviation is default (Continue) then
			// in classic theme the first tab header is closer to first qat control than the
			// second qat control. Meaning a right arrow key from first qat control takes focus
			// to first tab header which is not expected. Hence setting the direction navigation
			// to Local so that a local search in qat is made before making a ribbon wide search.
			KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));

			EventManager.RegisterClassHandler(ownerType, Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler(OnClickThroughThunk));
			EventManager.RegisterClassHandler(ownerType, Mouse.LostMouseCaptureEvent, new MouseEventHandler(OnLostMouseCaptureThunk), true /* handledEventsToo */);
			EventManager.RegisterClassHandler(ownerType, RibbonControlService.DismissPopupEvent, new RibbonDismissPopupEventHandler(OnDismissPopupThunk));
			EventManager.RegisterClassHandler(ownerType, Mouse.MouseDownEvent, new MouseButtonEventHandler(OnMouseDownThunk), true);
			EventManager.RegisterClassHandler(ownerType, FrameworkElement.ContextMenuOpeningEvent, new ContextMenuEventHandler(OnContextMenuOpeningThunk), true);
			EventManager.RegisterClassHandler(ownerType, FrameworkElement.ContextMenuClosingEvent, new ContextMenuEventHandler(OnContextMenuClosingThunk), true);
			EventManager.RegisterClassHandler(ownerType, KeyTipService.ActivatingKeyTipEvent, new ActivatingKeyTipEventHandler(OnActivatingKeyTipThunk));
			EventManager.RegisterClassHandler(ownerType, KeyTipService.PreviewKeyTipAccessedEvent, new KeyTipAccessedEventHandler(OnPreviewKeyTipAccessedThunk));
		}

		#endregion

		#region Public Properties

		/// <summary>
		///     DependencyProperty for Ribbon property.
		/// </summary>
		public static readonly DependencyProperty RibbonProperty =
			RibbonControlService.RibbonProperty.AddOwner(typeof(RibbonQuickAccessToolBar));

		/// <summary>
		///     This property is used to access visual style brushes defined on the Ribbon class.
		/// </summary>
		public Ribbon Ribbon
		{
			get { return RibbonControlService.GetRibbon(this); }
		}

		/// <summary>
		///   Gets or sets a value indicating whether or not the overflow popup is currently open.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool IsOverflowOpen
		{
			get { return (bool)GetValue(IsOverflowOpenProperty); }
			set { SetValue(IsOverflowOpenProperty, value); }
		}

		/// <summary>
		///   Using a DependencyProperty as the backing store for IsOverflowOpen.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty IsOverflowOpenProperty = DependencyProperty.Register(
			"IsOverflowOpen",
			typeof(bool),
			typeof(RibbonQuickAccessToolBar),
			new FrameworkPropertyMetadata(
				false,
				FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
				new PropertyChangedCallback(OnIsOverflowOpenChanged),
				new CoerceValueCallback(OnCoerceIsOverflowOpen)));

		private static void OnIsOverflowOpenChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;

			if (!qat.IsOverflowOpen)
			{
				// If the overflow drop down is closed due to
				// an action of context menu then ContextMenuClosed
				// event is never raised. Hence reset the flag.
				qat.InContextMenu = false;
			}

			UIElement popupChild = qat._overflowPopup.TryGetChild();
			RibbonHelper.HandleIsDropDownChanged(
				qat,
				delegate() { return qat.IsOverflowOpen; },
				popupChild,
				popupChild);

			if ((bool)(e.NewValue))
			{
				qat.RetainFocusOnEscape = RibbonHelper.IsKeyboardMostRecentInputDevice();
			}

			// Raise UI Automation Events
			RibbonQuickAccessToolBarAutomationPeer peer = UIElementAutomationPeer.FromElement(qat) as RibbonQuickAccessToolBarAutomationPeer;
			if (peer != null)
			{
				peer.RaiseExpandCollapseAutomationEvent(!(bool)e.OldValue, !(bool)e.NewValue);
			}
		}

		private static object OnCoerceIsOverflowOpen(DependencyObject d, object baseValue)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)d;
			if (!qat.HasOverflowItems)
			{
				return false;
			}
			return baseValue;
		}

		/// <summary>
		///   Gets a value indicating whether we have overflow items.
		/// </summary>
		public bool HasOverflowItems
		{
			get { return (bool)GetValue(HasOverflowItemsProperty); }
			internal set { SetValue(HasOverflowItemsPropertyKey, value); }
		}

		/// <summary>
		///   The DependencyPropertyKey needed to set read-only HasOverflowItems property.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		private static readonly DependencyPropertyKey HasOverflowItemsPropertyKey =
				DependencyProperty.RegisterReadOnly(
						"HasOverflowItems",
						typeof(bool),
						typeof(RibbonQuickAccessToolBar),
						new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnHasOverflowItemsChanged)));

		/// <summary>
		///   Using a DependencyProperty as the backing store for HasOverflowItems.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty HasOverflowItemsProperty =
				HasOverflowItemsPropertyKey.DependencyProperty;


		private static void OnHasOverflowItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;
			qat.CoerceValue(RibbonQuickAccessToolBar.IsOverflowOpenProperty);
		}

		#endregion

		#region IsOverflowItem

		/// <summary>
		///     The key needed set a read-only property.
		/// Attached property to indicate if the item is placed in the overflow panel
		/// </summary>
		internal static readonly DependencyPropertyKey IsOverflowItemPropertyKey =
				DependencyProperty.RegisterAttachedReadOnly(
						"IsOverflowItem",
						typeof(bool),
						typeof(RibbonQuickAccessToolBar),
						new FrameworkPropertyMetadata(false));

		/// <summary>
		///     The DependencyProperty for the IsOverflowItem property.
		///     Flags:              None
		///     Default Value:      false
		/// </summary>
		public static readonly DependencyProperty IsOverflowItemProperty =
				IsOverflowItemPropertyKey.DependencyProperty;

		/// <summary>
		/// Writes the attached property IsOverflowItem to the given element.
		/// </summary>
		/// <param name="element">The element to which to write the attached property.</param>
		/// <param name="value">The property value to set</param>
		internal static void SetIsOverflowItem(DependencyObject element, object value)
		{
			element.SetValue(IsOverflowItemPropertyKey, value);
		}

		/// <summary>
		/// Reads the attached property IsOverflowItem from the given element.
		/// </summary>
		/// <param name="element">The element from which to read the attached property.</param>
		/// <returns>The property's value.</returns>
		public static bool GetIsOverflowItem(DependencyObject element)
		{
			if (element == null)
			{
				throw new ArgumentNullException("element");
			}
			return (bool)element.GetValue(IsOverflowItemProperty);
		}

		#endregion

		#region Public Methods

		/// <summary>
		///   Invoked when the QuickAccessToolbar's template is applied.
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			if (_mainPanel != null)
			{
				_mainPanel.Children.Clear();
			}

			if (_overflowPanel != null)
			{
				_overflowPanel.Children.Clear();
			}

			_mainPanel = GetTemplateChild(MainPanelTemplateName) as RibbonQuickAccessToolBarPanel;
			_overflowPanel = GetTemplateChild(OverflowPanelTemplateName) as RibbonQuickAccessToolBarOverflowPanel;
			_overflowPopup = GetTemplateChild(OverflowPopupTemplateName) as Popup;
			_overflowButton = GetTemplateChild(OverflowButtonTemplateName) as RibbonToggleButton;
			if (_overflowButton != null)
			{
				_overflowButton.ToolTipTitle = _overflowButtonToolTipText;
			}

			CoerceValue(DataContextProperty);   // Pick up the Ribbon's DataContext.

			// Set KeyTipAutoGenerationElements property on self.
			IEnumerable<DependencyObject> keyTipAutoGenerationElements = new KeyTipAutoGenerationElements(this);
			KeyTipService.SetKeyTipAutoGenerationElements(this, keyTipAutoGenerationElements);
			if (_overflowPanel != null)
			{
				// Set KeyTipAutoGenerationElements property on overflow panel which helps
				// auto generation of keytips on elements of overflow.
				KeyTipService.SetKeyTipAutoGenerationElements(_overflowPanel, keyTipAutoGenerationElements);
			}
		}

		#endregion

		#region Protected Methods

		private void InvalidateLayout()
		{
			InvalidateMeasure();

			RibbonQuickAccessToolBarPanel toolBarPanel = this.MainPanel;
			if (toolBarPanel != null)
			{
				toolBarPanel.InvalidateMeasure();
			}
		}

		/// <summary>
		///   Invoked when the QuickAccessToolbar's items collection changes.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
		{
			InvalidateLayout();
			base.OnItemsChanged(e);
		}

		protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
		{
			return new Microsoft.Windows.Automation.Peers.RibbonQuickAccessToolBarAutomationPeer(this);
		}

		#endregion

		#region Private Methods

		private static object OnCoerceDataContext(DependencyObject d, object baseValue)
		{
			if (baseValue == null)
			{
				RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)d;
				if (qat.Ribbon != null)
				{
					return qat.Ribbon.DataContext;
				}
			}

			return baseValue;
		}

		#endregion Private Methods

		#region DismissPopup

		private static void OnClickThroughThunk(object sender, MouseButtonEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;
			qat.OnClickThrough(e);
		}

		private void OnClickThrough(MouseButtonEventArgs e)
		{
			UIElement popupChild = _overflowPopup.TryGetChild();
			RibbonHelper.HandleClickThrough(this, e, popupChild);
		}

		private static void OnLostMouseCaptureThunk(object sender, MouseEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;
			qat.OnLostMouseCaptureThunk(e);
		}

		private void OnLostMouseCaptureThunk(MouseEventArgs e)
		{
			UIElement popupChild = _overflowPopup.TryGetChild();
			RibbonHelper.HandleLostMouseCapture(
				this,
				e,
				delegate() { return (IsOverflowOpen && !InContextMenu); },
				delegate(bool value) { IsOverflowOpen = value; },
				popupChild,
				popupChild);
		}

		private static void OnDismissPopupThunk(object sender, RibbonDismissPopupEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;
			qat.OnDismissPopup(e);
		}

		private void OnDismissPopup(RibbonDismissPopupEventArgs e)
		{
			UIElement popupChild = _overflowPopup.TryGetChild();
			RibbonHelper.HandleDismissPopup(
				e,
				delegate(bool value) { IsOverflowOpen = value; },
				delegate(DependencyObject d) { return d == _overflowButton; },
				popupChild,
				this);
		}

		private static void OnMouseDownThunk(object sender, MouseButtonEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;
			qat.OnAnyMouseDown();
		}

		private void OnAnyMouseDown()
		{
			RetainFocusOnEscape = false;
		}

		protected override void OnKeyDown(KeyEventArgs e)
		{
			base.OnKeyDown(e);
			if (!e.Handled)
			{
				RibbonHelper.HandleDropDownKeyDown(
					this,
					e,
					delegate { return IsOverflowOpen; },
					delegate(bool value) { IsOverflowOpen = value; },
					RetainFocusOnEscape ? _overflowButton : null,
					_overflowPopup.TryGetChild());
			}
		}

		#endregion DismissPopup

		#region ContextMenu

		private static void OnContextMenuOpeningThunk(object sender, ContextMenuEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;
			qat.OnContextMenuOpeningInternal();
		}

		private void OnContextMenuOpeningInternal()
		{
			InContextMenu = true;
		}

		private static void OnContextMenuClosingThunk(object sender, ContextMenuEventArgs e)
		{
			RibbonQuickAccessToolBar qat = (RibbonQuickAccessToolBar)sender;
			qat.OnContextMenuClosingInternal();
		}

		private void OnContextMenuClosingInternal()
		{
			InContextMenu = false;
			if (IsOverflowOpen)
			{
				UIElement popupChild = _overflowPopup.TryGetChild();
				RibbonHelper.AsyncSetFocusAndCapture(
					this,
					delegate() { return IsOverflowOpen; },
					popupChild,
					popupChild);
			}
		}

		#endregion ContextMenu

		#region ContainerGeneration

		protected override DependencyObject GetContainerForItemOverride()
		{
			return new RibbonControl();
		}

		protected override bool IsItemItsOwnContainerOverride(object item)
		{
			return item is RibbonControl;
		}

		protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
		{
			base.PrepareContainerForItemOverride(element, item);

			RibbonControl ribbonControl = (RibbonControl)element;
			ribbonControl.IsInQuickAccessToolBar = true;
			ribbonControl.Margin = new Thickness(3, 0, 3, 0);

			if (item is FrameworkElement)
			{
				FrameworkElement fElem = item as FrameworkElement;

				fElem.Margin = new Thickness(0);
				fElem.Height = 22;

				Binding isEnabledBinding = new Binding("IsEnabled");
				isEnabledBinding.ElementName = fElem.Name;
				fElem.SetBinding(IsEnabledProperty, isEnabledBinding);

				if (!(item is RibbonSplitButton))
					fElem.Width = 22;

				if (item is ToggleButton)
				{
					Binding isCheckedBinding = new Binding("IsChecked");

					ToggleButton toggleButton = item as ToggleButton;
					isCheckedBinding.ElementName = fElem.Name;
					toggleButton.SetBinding(ToggleButton.IsCheckedProperty, isCheckedBinding);

					//
					// BUG FIX: Prevent state where neither the QAT button nor the Ribbon button
					// can be checked, since they both trigger the other.
					//
					if (item is RadioButton)
						(item as RadioButton).GroupName = null;
				}
			}

			ribbonControl.Content = item;
		}

		protected override void ClearContainerForItemOverride(DependencyObject element, object item)
		{
			base.ClearContainerForItemOverride(element, item);

			RibbonHelper.ClearPseudoInheritedProperties(element);
		}

		#endregion ContainerGeneration

		#region CloneEvent

		public static readonly RoutedEvent CloneEvent = EventManager.RegisterRoutedEvent("Clone", RoutingStrategy.Bubble, typeof(RibbonQuickAccessToolBarCloneEventHandler), typeof(RibbonQuickAccessToolBar));

		public static void AddCloneHandler(DependencyObject element, RibbonQuickAccessToolBarCloneEventHandler handler)
		{
			RibbonHelper.AddHandler(element, CloneEvent, handler);
		}

		public static void RemoveCloneHandler(DependencyObject element, RibbonQuickAccessToolBarCloneEventHandler handler)
		{
			RibbonHelper.RemoveHandler(element, CloneEvent, handler);
		}

		#endregion

		#region ID

		// Determine whether the QAT contains an element with the given QAT ID.
		internal bool ContainsId(object targetID)
		{
			foreach (object o in this.Items)
			{
				DependencyObject dependencyObject = o as DependencyObject;
				if (dependencyObject != null)
				{
					object currentID = RibbonControlService.GetQuickAccessToolBarId(dependencyObject);
					if (object.Equals(currentID, targetID))
					{
						return true;
					}
				}
			}

			return false;
		}

		#endregion ID

		#region ContentModel

		public static readonly DependencyProperty CustomizeMenuButtonProperty =
			DependencyProperty.Register(
					"CustomizeMenuButton",
					typeof(RibbonMenuButton),
					typeof(RibbonQuickAccessToolBar));

		public RibbonMenuButton CustomizeMenuButton
		{
			get { return (RibbonMenuButton)GetValue(CustomizeMenuButtonProperty); }
			set { SetValue(CustomizeMenuButtonProperty, value); }
		}

		#endregion ContentModel

		#region KeyTips

		#region KeyTipAutoGenerationElements

		private class KeyTipAutoGenerationElements : IEnumerable<DependencyObject>
		{
			#region Constructor And Properties

			public KeyTipAutoGenerationElements(RibbonQuickAccessToolBar quickAccessToolBar)
			{
				QuickAccessToolBar = quickAccessToolBar;
			}

			RibbonQuickAccessToolBar QuickAccessToolBar
			{
				get;
				set;
			}

			#endregion

			#region IEnumerable<DependencyObject> Members

			public IEnumerator<DependencyObject> GetEnumerator()
			{
				int itemCount = QuickAccessToolBar.Items.Count;
				int overflowStartIndex = -1;

				// Set KeyTip for all non-overflow items
				for (int i = 0; i < itemCount; i++)
				{
					RibbonControl ribbonControl = QuickAccessToolBar.ItemContainerGenerator.ContainerFromIndex(i) as RibbonControl;
					if (ribbonControl != null)
					{
						if (GetIsOverflowItem(ribbonControl))
						{
							overflowStartIndex = i;
							break;
						}
						else if (ribbonControl.IsVisible)
						{
							UIElement contentChild = ribbonControl.ContentChild;
							if (contentChild != null &&
								contentChild.IsVisible &&
								PropertyHelper.IsDefaultValue(contentChild, KeyTipService.KeyTipProperty))
							{
								yield return contentChild;
							}
						}
					}
				}

				if (overflowStartIndex != -1)
				{
					// Set KeyTip for overflow items.
					for (int i = overflowStartIndex; i < itemCount; i++)
					{
						RibbonControl ribbonControl = QuickAccessToolBar.ItemContainerGenerator.ContainerFromIndex(i) as RibbonControl;
						if (ribbonControl != null &&
							ribbonControl.Visibility == Visibility.Visible &&
							GetIsOverflowItem(ribbonControl))
						{
							UIElement contentChild = ribbonControl.ContentChild;
							if (contentChild != null &&
								contentChild.Visibility == Visibility.Visible &&
								PropertyHelper.IsDefaultValue(contentChild, KeyTipService.KeyTipProperty))
							{
								yield return contentChild;
							}
						}
					}
				}
			}

			#endregion

			#region IEnumerable Members

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return this.GetEnumerator();
			}

			#endregion
		}

		#endregion

		private static void OnActivatingKeyTipThunk(object sender, ActivatingKeyTipEventArgs e)
		{
			((RibbonQuickAccessToolBar)sender).OnActivatingKeyTip(e);
		}

		protected virtual void OnActivatingKeyTip(ActivatingKeyTipEventArgs e)
		{
			if (e.OriginalSource == _overflowButton)
			{
				if (_overflowButton.IsVisible)
				{
					RibbonHelper.SetDefaultQatKeyTipPlacement(e);
				}
				else
				{
					e.KeyTipVisibility = Visibility.Collapsed;
				}
			}
		}

		private static void OnPreviewKeyTipAccessedThunk(object sender, KeyTipAccessedEventArgs e)
		{
			((RibbonQuickAccessToolBar)sender).OnPreviewKeyTipAccessed(e);
		}

		protected virtual void OnPreviewKeyTipAccessed(KeyTipAccessedEventArgs e)
		{
			if (e.OriginalSource == _overflowButton)
			{
				// Handle KeyTip accessed for overflow button.
				if (HasOverflowItems &&
					!IsOverflowOpen)
				{
					IsOverflowOpen = true;
					UIElement popupChild = _overflowPopup.TryGetChild();
					if (popupChild != null)
					{
						KeyTipService.SetIsKeyTipScope(popupChild, true);
						e.TargetKeyTipScope = popupChild;
					}
				}
				e.Handled = true;
			}
		}

		#endregion
	}

}
